Skip to content

Conversation

@delcypher
Copy link

This patch implements an instrumentation plugin for the
-fbounds-safety soft trap mode first implemented in
#11645 (rdar://158088757).

The current implementation of -fbounds-safety traps works by emitting
calls to runtime functions intended to log the occurrence of a soft trap.
While the user could just set a breakpoint of these functions the
instrumentation plugin sets it automatically and provides several
additional features:

When debug info is available:

  • It adjusts the stop reason to be the reason for trapping. This is
    extracted from the artificial frame in the debug info (similar to
    -fbounds-safety hard traps).
  • It adjusts the selected frame to be the frame where the soft trap
    occurred.

When debug info is not available:

  • For the call-with-str soft trap mode the soft trap reason is
    read from the first argument register.
  • For the call-minimal soft trap mode the stop reason is adjusted
    to note its a bounds check failure but does not give further
    information because none is available.
  • In this situation the selected frame is not adjusted because in
    this mode the user will be looking at assembly and adjusting the
    frame makes things confusing.

This patch includes shell and api tests. The shell tests seemed like the
best way to test behavior when debug info is missing because those tests
make it easy to disable building with debug info completely.

rdar://163230807

This refactors the soft trap runtime test so the soft trap runtime
implementation exists in its own file. This has several advantages:

* It let's the runtime be built without debug info which will be the
  common case uses hit
* It prevents the risk of infinite recursion because it isn't safe
  to build the soft trap runtime with -fbounds-safety soft trap mode
  enabled.
@delcypher delcypher self-assigned this Nov 14, 2025
@delcypher delcypher added the clang:bounds-safety Issue relating to the experimental -fbounds-safety feature in Clang label Nov 14, 2025
@delcypher delcypher force-pushed the dliew/rdar-163230807-wip branch from f7206d3 to a15e413 Compare November 15, 2025 00:22
@delcypher
Copy link
Author

@swift-ci test

@delcypher
Copy link
Author

@swift-ci test llvm

…fety soft traps

This patch implements an instrumentation plugin for the
`-fbounds-safety` soft trap mode first implemented in
swiftlang#11645 (rdar://158088757).

The current implementation of -fbounds-safety traps works by emitting
calls to runtime functions intended to log the occurrence of a soft trap.
While the user could just set a breakpoint of these functions the
instrumentation plugin sets it automatically and provides several
additional features:

When debug info is available:

* It adjusts the stop reason to be the reason for trapping. This is
  extracted from the artificial frame in the debug info (similar to
  -fbounds-safety hard traps).
* It adjusts the selected frame to be the frame where the soft trap
  occurred.

When debug info is not available:

* For the `call-with-str` soft trap mode the soft trap reason is
  read from the first argument register.
* For the `call-minimal` soft trap mode the stop reason is adjusted
  to note its a bounds check failure but does not give further
  information because none is available.
* In this situation the selected frame is not adjusted because in
  this mode the user will be looking at assembly and adjusting the
  frame makes things confusing.

This patch includes shell and api tests. The shell tests seemed like the
best way to test behavior when debug info is missing because those tests
make it easy to disable building with debug info completely.

rdar://163230807
@delcypher delcypher force-pushed the dliew/rdar-163230807-wip branch from a15e413 to c8af09b Compare November 15, 2025 02:09
@delcypher
Copy link
Author

@swift-ci test

@delcypher
Copy link
Author

@swift-ci test llvm

@delcypher
Copy link
Author

delcypher commented Nov 16, 2025

Windows failure looks completely unrelated to this PR:

21:30:12  C:\Program Files\CMake\bin\cmake.exe -B T:\x86_64-unknown-windows-msvc\DocC -S C:\Users\swift-ci\jenkins\workspace\apple-llvm-project-pull-request-windows\swift-docc -G Ninja -D ArgumentParser_DIR=T:/x86_64-unknown-windows-msvc/ArgumentParser/cmake/modules -D BUILD_SHARED_LIBS=YES -D CMAKE_BUILD_TYPE=Release -D CMAKE_C_COMPILER=T:/5/bin/clang-cl.exe -D CMAKE_C_COMPILER_TARGET=x86_64-unknown-windows-msvc -D CMAKE_C_FLAGS=/GS- /Gw /Gy /Oy /Oi /Zc:inline -D CMAKE_EXE_LINKER_FLAGS=/INCREMENTAL:NO /OPT:REF /OPT:ICF -D CMAKE_FIND_PACKAGE_PREFER_CONFIG=YES -D CMAKE_INSTALL_PREFIX=T:/Program Files/Swift/Toolchains/0.0.0+Asserts/usr -D CMAKE_MAKE_PROGRAM=C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/CMake/Ninja/ninja.exe -D CMAKE_SHARED_LINKER_FLAGS=/INCREMENTAL:NO /OPT:REF /OPT:ICF -D CMAKE_STATIC_LIBRARY_PREFIX_Swift=lib -D CMAKE_Swift_COMPILER=T:/5/bin/swiftc.exe -D CMAKE_Swift_COMPILER_TARGET=x86_64-unknown-windows-msvc -D CMAKE_Swift_COMPILER_WORKS=YES -D CMAKE_Swift_FLAGS=-sdk \"T:/Program Files/Swift/Platforms/Windows.platform/Developer/SDKs/Windows.sdk\" -gnone -Xlinker /INCREMENTAL:NO -Xlinker /OPT:REF -Xlinker /OPT:ICF -D CMAKE_Swift_FLAGS_RELEASE=-O -D CMAKE_Swift_FLAGS_RELWITHDEBINFO=-O -D cmark-gfm_DIR=T:/Program Files/Swift/Toolchains/0.0.0+Asserts/usr/lib/cmake -D LMDB_DIR=T:/x86_64-unknown-windows-msvc/LMDB/cmake/modules -D SwiftASN1_DIR=T:/x86_64-unknown-windows-msvc/ASN1/cmake/modules -D SwiftCrypto_DIR=T:/x86_64-unknown-windows-msvc/Crypto/cmake/modules -D SwiftMarkdown_DIR=T:/x86_64-unknown-windows-msvc/Markdown/cmake/modules -D SymbolKit_DIR=T:/x86_64-unknown-windows-msvc/SymbolKit/cmake/modules
21:30:12  -- The C compiler identification is Clang 21.1.6 with MSVC-like command-line
21:30:12  -- The Swift compiler identification is Apple 6.3
21:30:13  -- Detecting C compiler ABI info
21:30:13  -- Detecting C compiler ABI info - done
21:30:13  -- Check for working C compiler: T:/5/bin/clang-cl.exe - skipped
21:30:13  -- Detecting C compile features
21:30:13  -- Detecting C compile features - done
21:30:13  CMake Error at Sources/CMakeLists.txt:11 (add_subdirectory):
21:30:13    The source directory
21:30:13  
21:30:13      C:/Users/swift-ci/jenkins/workspace/apple-llvm-project-pull-request-windows/swift-docc/Sources/SwiftDocCUtilities
21:30:13  
21:30:13    does not contain a CMakeLists.txt file.
21:30:13  
21:30:13  
21:30:13  -- Configuring incomplete, errors occurred!
21:30:13  Error: Error: cmake.exe exited with code 1.

likely caused by swiftlang/swift-docc#1331. Looks like swiftlang/swift-docc#1352 is out to revert this change.

@delcypher
Copy link
Author

@swift-ci please test windows platform

case ArchSpec::eCore_x86_32_i686:
// Technically some x86 calling conventions do use a register for
// passing the first argument but let's ignore that for now.
return {};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth explaining here that you can't get the information for this architecture? Otherwise the person receiving this report might waste time trying to figure out why this wasn't working in their particular case?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If LLDB has a clean way of communicating this to the user I'm willing to use it (e.g. some kind of diagnostic system). At the moment is seems like the only thing I can do is shoehorn the failure into the trap reason message and I think that would be confusing. E.g.:

stop reason = Soft Bounds check failed: Cannot determine trap reason on x86 architecture when debug info is missing

The Cannot determ... part of the message is supposed to be read as the specific reason for trapping normally. Reusing it here to explain why the trap reason couldn't be determined seems a bit confusing.

Ideally what is something like:

stop reason = Soft Bounds check failed
note: Cannot determine trap reason on x86 architecture when debug info is missing

but I don't think anything like this exists today.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can write warning messages directly by writing to the stream returned by Debugger::GetAsyncErrorStream, or you can post a warning event using Debugger::ReportWarning. I think posting a warning event is currently in favor.

auto process = thread_sp->GetProcess();
if (!process)
return {};
switch (process->GetTarget().GetArchitecture().GetCore()) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure you need to do this for this commit, but it would be clearer if the architecture told you whether it uses registers for argument passing, than having to hard code it here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can file a github issue for this. Should this go to the upstream LLVM issue tracker or the Swift issue tracker?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

llvm issue tracker, this is architecture and not swift specific.

// Examine the register for the first argument
auto *arg0_info = rc->GetRegisterInfo(
lldb::RegisterKind::eRegisterKindGeneric, LLDB_REGNUM_GENERIC_ARG1);
if (!arg0_info)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along this process there are a bunch of places where it could fail. It might help your sanity later on if you logged where the failure occurs. Then when this is failing on a machine or for a process you can't get access to you have more of a chance of figuring out what went wrong.

Copy link
Author

@delcypher delcypher Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What channel should I use? Presumably you mean logging code like this (first thing I found when greping for LLDB_LOG) ?

Log *log = GetLog(LLDBLog::API);
LLDB_LOGF(log,
            "SBDebugger(%p)::CreateTarget (filename=\"%s\", triple=%s, "
            "platform_name=%s, add_dependent_modules=%u, error=%s) => "
            "SBTarget(%p)",
            static_cast<void *>(m_opaque_sp.get()), filename, target_triple,
            platform_name, add_dependent_modules, sb_error.GetCString(),
            static_cast<void *>(target_sp.get()));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL, there isn't an instrumentation runtime log channel, which there really should be. So maybe better to do this as a follow-on.

return lldb::eInstrumentationRuntimeTypeBoundsSafety;
}

const RegularExpression &
Copy link

@jimingham jimingham Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Julian just added a InstrumentationRuntime::MatchAllModules. You still need to override this function but if you also override MatchAllModules to return true, we won't waste time pointlessly trying an empty regex against all the libraries.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The override for MatchAllModules is declared in the header for InstrumentationRuntimeBoundsSafety

return lldb::eInstrumentationRuntimeTypeBoundsSafety;
}

const RegularExpression &
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The override for MatchAllModules is declared in the header for InstrumentationRuntimeBoundsSafety

lldb::user_id_t break_id,
lldb::user_id_t break_loc_id);

bool MatchAllModules() override { return true; }
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jimingham The override is here. That should be sufficient right?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, missed it. Yes, that's good.

// Examine the register for the first argument
auto *arg0_info = rc->GetRegisterInfo(
lldb::RegisterKind::eRegisterKindGeneric, LLDB_REGNUM_GENERIC_ARG1);
if (!arg0_info)
Copy link
Author

@delcypher delcypher Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What channel should I use? Presumably you mean logging code like this (first thing I found when greping for LLDB_LOG) ?

Log *log = GetLog(LLDBLog::API);
LLDB_LOGF(log,
            "SBDebugger(%p)::CreateTarget (filename=\"%s\", triple=%s, "
            "platform_name=%s, add_dependent_modules=%u, error=%s) => "
            "SBTarget(%p)",
            static_cast<void *>(m_opaque_sp.get()), filename, target_triple,
            platform_name, add_dependent_modules, sb_error.GetCString(),
            static_cast<void *>(target_sp.get()));

auto process = thread_sp->GetProcess();
if (!process)
return {};
switch (process->GetTarget().GetArchitecture().GetCore()) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can file a github issue for this. Should this go to the upstream LLVM issue tracker or the Swift issue tracker?

case ArchSpec::eCore_x86_32_i686:
// Technically some x86 calling conventions do use a register for
// passing the first argument but let's ignore that for now.
return {};
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If LLDB has a clean way of communicating this to the user I'm willing to use it (e.g. some kind of diagnostic system). At the moment is seems like the only thing I can do is shoehorn the failure into the trap reason message and I think that would be confusing. E.g.:

stop reason = Soft Bounds check failed: Cannot determine trap reason on x86 architecture when debug info is missing

The Cannot determ... part of the message is supposed to be read as the specific reason for trapping normally. Reusing it here to explain why the trap reason couldn't be determined seems a bit confusing.

Ideally what is something like:

stop reason = Soft Bounds check failed
note: Cannot determine trap reason on x86 architecture when debug info is missing

but I don't think anything like this exists today.


add_subdirectory(ASan)
add_subdirectory(ASanLibsanitizers)
# TO_UPSTREAM(BoundsSafety) ON

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a convention we follow for -fbounds-safety code. It's mostly useful for people resolving merge conflicts so they can clearly see what the code causing the conflict is for.

If you would prefer to not have it I can remove it but personally I find is useful.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this context it just leaves me confused why BoundsSafety is special in this regard? Why isn't this around other InstrumentationRuntimes, what does it do, etc.
But also, I just did

find . -type f -exec grep TO_UPSTREAM {} +

at the top-level llvm-project directory and got no hits. This certainly shouldn't be the first - unexplained - instance of it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:bounds-safety Issue relating to the experimental -fbounds-safety feature in Clang

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants